/**
 * @file
 * @date 2020-10-14
 * @brief player state class for clearNRG event
 */

#pragma once

#include "lab.h"
#include <vector>
#include <map>
#include <chrono>

/**
 * @class State
 * @brief Describe player state in Clear Energy event
 * 
 * - Player have 3 buildings, 8 researches
 * - Buildings produce 3 resources: power, assets, xp
 * - Event tier increase resource production
 * - Playthrough counter -  price coming up every time when player complete event
 * - Main goal - claim 30 orbs, need total 1111111111000 power
 */
class State
{
public:
    /**
     * @brief Construct State object
     * @param   aTier           custom event tier
     * @param   aPlaythrough    custom playthrough
     */
    explicit State( double aTier = 1, int aPlaythrough = 0 );

    /**
     * @brief Calculate basic production per minute
     * @param   type    building type
     * @param   level   building level
     * @param   tier    event tier
     * @return amount of basic production per minute
     */
    static double getBasicProduction( UpgradeType type, int64_t level, double tier );

    /**
     * @brief Calculate total production per minute
     * @param   base    if true show only base production without efficiency multiplier
     * @return production per minute
     */
    double getTotalProduction( bool base = false ) const;

    /**
     * @brief Calculate production of specified type product
     * @param   type    product type
     * @return production per minute
     */
    double getTotalProductionType( ProductType type ) const;

    /**
     * @brief Change level of specified type upgrade
     * @param   type        upgrade type
     * @param   level       level value
     * @param   overwrite   overwrite on true, increase on false
     * @return level value after upgrade
     */
    int64_t upgrade( UpgradeType type, int64_t level, bool overwrite = false );

    /**
     * @brief Spend all custom resource to upgrade
     * @param   resource    resource type
     * @return list of upgraded entities
     */
    std::map<UpgradeType, int64_t> upgradeAll(ProductType resource);

    /**
     * @brief Calculate upgrade price of specified type upgrade for next @p count levels
     * @param   type    upgrade type
     * @param   count   level count
     * @return upgrade price for next @p count levels
     */
    double upgradePrice( UpgradeType type, int64_t count ) const;

    /**
     * @brief Calculate upgrade price of specified type upgrade from @p begin to @p begin levels
     * @param   type    upgrade type
     * @param   begin   begin level
     * @param   end     end level
     * @return upgrade price
     */
    double upgradePrice( UpgradeType type, int64_t begin, int64_t end ) const;

    /**
     * @brief Get upgrade of specified type
     * @param   type    upgrade type
     * @return const reference to Upgrade object
     */
    const Upgrade & getType( UpgradeType type ) const;

    /**
     * @brief Get product of specified type
     * @param   type    product type
     * @return const reference to Product object
     */
    const Product & getType( ProductType type ) const;

    /**
     * @brief Get current step value of specified type upgrade
     * @param   type    upgrade type
     * @return current step value
     * @note for UpgradeType::Cost_Reduction_Assets and UpgradeType::Cost_Reduction_Orb step is always 1
     */
    int getStep( UpgradeType type ) const;

    /**
     * @brief get Optimal step value based on product values
     * @return optimal step value. [1, 10, 100]
     */
    int getStepOptimal() const;

    /**
     * @brief Get current tier
     * @return current event tier
     */
    double getTier() const;

    /**
     * @brief Get current playthrough
     * @return current game playthrough
     */
    int getPlaythrough() const;

    /**
     * @brief Set tier
     * @param aTier new tier value
     */
    void setTier( double aTier );

    /**
     * @brief Change playthrough to a new value
     * @param p new playthrough value
     */
    void setPlaythrough( int p );

    /**
     * @brief Change default step size
     * @param s new step size
     */
    void setStep( int s );

    /**
     * @brief Overload with std::chrono::seconds
     * @param seconds how many seconds to skip
     */
    void tick( const std::chrono::seconds & seconds );

    /**
     * @brief change control mode
     * @param   control aut control mode, true = enable, false = disable
     * @details  when auto control enable next fields will changed automatically:
     * - @f$ Weight_{power} = f(Production_{power}, Value_{power}, Factor_{power} ) @f$
     * - @f$ Weight_{assets} = f(Priorities, Factor_{assets} ) @f$
     * - Step size on calculate remaining time
     * @sa getAutoControl
     */
    void setAutoControl( bool control );

    /**
     * @brief auto control weights or not
     * @return true on auto control, false on manual
     * @sa setAutoControl
     */
    bool getAutoControl() const;

    /**
     * @brief Fast forward to the event end
     * @param   sleep   minutes for sleep
     * @param   check   how often use can check game state
     * @return remainings time in minutes before event will complete
     * @note event complete when player claim 30 orbs
     */
    std::chrono::minutes completeEvent( std::chrono::minutes sleep, std::chrono::minutes check );

    /**
     * @brief Calculate minimal power weight
     * @return minimal power weight
     * Calculation based only on current ProductType::Power value and production
     * @sa getTotalProductionType
     */
    double optimalPowerWeight() const;

    /**
     * @brief change factor of specified type product
     * @param   type    product type
     * @param   factor  new factor value
     */
    void setProductFactor( ProductType type, double factor );

    /**
     * @brief change weight of specified product type
     * @param   type    product type
     * @param   weight  new weight value
     */
    void setProductWeight( ProductType type, double weight );

    /**
     * @brief change value of specified type product
     * @param   type    product type
     * @param   value   product value
     */
    void setProductValue( ProductType type, double value );

    /**
     * @brief change count of claimed orbs
     * @param   orbs    new orbs count
     */
    void claimOrbs( int orbs );

private:
    int step = 1;                                           ///< default upgrade step
    double tier = 1;                                        ///< event tier, 1 tier give + 10% speed increase
    int playthrough = 0;                                    ///< event complete count, increase upgrade cost
    PowerOrb orb;                                           ///< power orbs
    std::vector<Upgrade> buildings;
    std::vector<Upgrade> researches;
    std::vector<Product> products;

    bool autoControl = true;                                ///< auto control product's weight

    double netGain( UpgradeType );

    /**
     * @brief Get upgrade of specified @p type 
     * @param   type    building type
     * @return reference to Upgrade object
     */
    Upgrade & getT( UpgradeType type );
    /**
     * @brief Get product of specified type
     * @param   type    product type
     * @return reference to Product object
     */
    Product & getT( ProductType type );

    /**
     * @brief Recalculate priority for each upgrade type
     * @note This function must be called after any changes(level, product weight, product amount, etc)!
     */
    void updateUpgradePriority();

    /**
     * @brief Calculate time to complete event with ProductType::Power production
     * @return Remaining time in minutes
     */
    std::chrono::minutes remainingTime() const;
};
